今天要把之前的功能串接至API,進而透過API來擷取指定frame的圖片或GIF,首先我們要先定義API的request與response格式,接著再來實作API的handler
mygo
├── backend
│ ├── backend.go
│ ├── go.mod
│ ├── go.sum
│ ├── payload.go
│ └── route.go
├── data
│ ├── data.go
│ ├── db.go
│ ├── go.mod
│ └── go.sum
├── data.json
├── go.mod
├── go.sum
├── main.go
├── mygo
├── README.md
├── static
│ ├── index.css
│ └── index.js
├── templates
│ └── index.html
└── video
├── go.mod
├── go.sum
└── video.go
backend.go
為API的初始化設定、指定route中的handler,並mapping到指定的uripayload.go
為API的request與response格式,以標記JSON Tag的struct來定義route.go
為API的handler/api/search
{
"query": "string",
"episode": "string",
"nth_page": 0,
"paged_by": 0
}
{
"results": [
{
"episode": "string",
"frame_start": 0,
"frame_end": 0,
"text": "string",
"segment_id": 0
}
]
}
/api/extract_frame
{
"episode": "string",
"frame": 0
}
{
"image": "string" // base64 encoded image
}
/api/extract_gif
{
"episode": "string",
"frame": 0
}
{
"gif": "string" // base64 encoded image
}
確定架構後就可以開始實作API了
mygo/backend/payload.go
package backend
import (
"mygo/data"
)
// request of /api/search
type SearchRequest struct {
Query string `json:"query"`
Episode string `json:"episode"`
NthPage int `json:"nth_page"`
PagedBy int `json:"paged_by"`
}
// response of /api/search
type SearchResponse struct {
Count int `json:"count"`
Results []data.SentenceItem `json:"results"`
}
// request of /api/extract_frame
type ExtraceFrameRequest struct {
Episode string `json:"episode"`
FrameNumber int `json:"frame"`
}
// response of /api/extract_frame
type ExtraceFrameResponse struct {
Frame string `json:"frame"`
}
// request of /api/extract_gif
type ExtraceGIFRequest struct {
Episode string `json:"episode"`
Start int `json:"start"`
End int `json:"end"`
}
// response of /api/extract_gif
type ExtraceGIFResponse struct {
GIF string `json:"gif"`
}
field的名稱其實沒差,只要payload符合JSON tag的名稱,還有類型符合就可以了
mygo/backend/route.go
package backend
import (
"encoding/base64"
"fmt"
"mygo/data"
"mygo/video"
"net/http"
"os"
"runtime"
"github.com/gin-gonic/gin"
)
const (
pagedBy = 20
videoPath = "%s/mygo-anime/%s.mp4" // $HOME/mygo-anime/%$episode.mp4
)
var homePath = os.Getenv("HOME")
func Search(c *gin.Context) {
var req SearchRequest
if err := c.ShouldBindBodyWithJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if req.PagedBy == 0 {
req.PagedBy = pagedBy
}
result, count, err := data.SearchByText(req.Query, req.Episode, req.PagedBy, req.NthPage)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, SearchResponse{Results: result, Count: int(count)})
}
func ExtractFrame(c *gin.Context) {
var req ExtraceFrameRequest
if err := c.ShouldBindBodyWithJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if runtime.GOOS == "windows" {
homePath = os.Getenv("USERPROFILE")
}
videoPath := fmt.Sprintf(videoPath, homePath, req.Episode)
frame, fps := video.FetchVideoFPS(videoPath)
if frame < req.FrameNumber {
c.JSON(http.StatusBadRequest, gin.H{"error": "frame number out of range"})
return
}
buf, err := video.ExtractFrame(req.Episode, req.FrameNumber, fps)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, ExtraceFrameResponse{Frame: base64.StdEncoding.EncodeToString(buf.Bytes())})
}
func ExtractGIF(c *gin.Context) {
var req ExtraceGIFRequest
if err := c.ShouldBindBodyWithJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if runtime.GOOS == "windows" {
homePath = os.Getenv("USERPROFILE")
}
videoPath := fmt.Sprintf(videoPath, homePath, req.Episode)
frame, fps := video.FetchVideoFPS(videoPath)
if req.Start < 0 || req.End < 0 || req.End > frame {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid start or end frame"})
return
}
buf, err := video.ExtractGIF(req.Episode, req.Start, req.End, fps)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, ExtraceGIFResponse{GIF: base64.StdEncoding.EncodeToString(buf.Bytes())})
}
Search
handler
ShouldBindBodyWithJSON
將request的body bind到SearchRequest
struct中data.SearchByText
來取得搜尋結果pagedBy
筆資料)ExtractFrame
handler
ShouldBindBodyWithJSON
將request的body bind到ExtraceFrameRequest
struct中video.FetchVideoFPS
來取得影片的fpsvideo.ExtractFrame
來取得指定frame的圖片ExtractGIF
handler
ShouldBindBodyWithJSON
將request的body bind到ExtraceGIFRequest
struct中video.FetchVideoFPS
來取得影片的fpsvideo.ExtractGIF
來取得指定frame範圍的GIFmygo/backend/backend.go
package backend
import (
"github.com/gin-gonic/gin"
)
func CreateEngine() *gin.Engine {
var r = gin.Default()
r.POST("/api/search", Search)
r.POST("/api/extract_frame", ExtractFrame)
r.POST("/api/extract_gif", ExtractGIF)
return r
}
mygo/main.go
package main
import (
"mygo/backend"
)
func main() {
r := backend.CreateEngine()
r.Run(":8080")
}
最後透過go run main.go
或是go build
後執行./mygo
來啟動API,接著就可以透過Postman或是其他工具來測試API了
Frame Result
GIF Response
那麼今天的文章就到這告一段落,如果我的文章有任何地方有錯誤請在留言區反應
明天就是最後一天了,我們一起加油吧!